Шаблоны простых элементов веб-страниц
Шаблоны простых элементов веб-страниц
Простые элементы веб-страниц составляют основу любого пользовательского интерфейса. Они обеспечивают взаимодействие пользователя с системой, позволяют вводить данные, просматривать информацию и управлять приложением. Эти компоненты универсальны: их можно встретить на сайтах электронной коммерции, в личных кабинетах, административных панелях, формах обратной связи и множестве других контекстов.
Настоящая глава представляет собой справочник по наиболее часто используемым шаблонам HTML-элементов, дополненных базовой стилизацией через CSS и минимальной серверной логикой на PHP. Все примеры написаны с учётом современных стандартов веб-разработки, семантической корректности и доступности (accessibility). Каждый шаблон автономен, легко копируется и адаптируется под конкретные задачи.
Форма входа (Login Form)
Форма входа — один из самых распространённых элементов веб-интерфейсов. Она позволяет пользователю аутентифицироваться в системе с использованием логина (или email) и пароля.
Структура HTML
<form id="loginForm" method="POST" action="login.php" novalidate>
<h2>Вход в систему</h2>
<div class="form-group">
<label for="email">Электронная почта</label>
<input type="email" id="email" name="email" required autocomplete="email">
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<div class="form-group form-check">
<input type="checkbox" id="remember" name="remember">
<label for="remember">Запомнить меня</label>
</div>
<button type="submit">Войти</button>
<p class="form-footer">
<a href="forgot-password.php">Забыли пароль?</a> |
<a href="register.php">Создать аккаунт</a>
</p>
</form>
Базовый CSS
#loginForm {
max-width: 400px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
font-family: Arial, sans-serif;
}
#loginForm h2 {
text-align: center;
margin-bottom: 1.5rem;
color: #333;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #444;
}
.form-group input[type="email"],
.form-group input[type="password"] {
width: 100%;
padding: 0.6rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}
.form-check {
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-check input[type="checkbox"] {
margin: 0;
}
button[type="submit"] {
width: 100%;
padding: 0.8rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
button[type="submit"]:hover {
background-color: #0056b3;
}
.form-footer {
text-align: center;
margin-top: 1rem;
font-size: 0.9rem;
}
.form-footer a {
color: #007bff;
text-decoration: none;
}
.form-footer a:hover {
text-decoration: underline;
}
Пример обработки на PHP (login.php)
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
// Валидация
if (empty($email) || empty($password)) {
die('Все поля обязательны для заполнения.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}
// Здесь должна быть проверка в базе данных
// Пример условного логина:
if ($email === 'user@example.com' && $password === 'secret123') {
$_SESSION['user_email'] = $email;
if ($remember) {
setcookie('user_email', $email, time() + 30 * 24 * 3600, '/');
}
header('Location: dashboard.php');
exit;
} else {
echo 'Неверные учетные данные.';
}
}
?>
📌 Внимание
В реальных проектах пароли никогда не хранятся в открытом виде. Используйтеpassword_hash()иpassword_verify()для безопасной работы с паролями.
Таблица с данными
Таблицы используются для отображения структурированной информации: списков пользователей, товаров, заказов и т.д.
HTML
<table class="data-table">
<caption>Список пользователей</caption>
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Имя</th>
<th scope="col">Email</th>
<th scope="col">Роль</th>
<th scope="col">Действия</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="ID">1</td>
<td data-label="Имя">Анна Петрова</td>
<td data-label="Email">anna@example.com</td>
<td data-label="Роль">Администратор</td>
<td data-label="Действия">
<button class="btn btn-edit">Редактировать</button>
<button class="btn btn-delete">Удалить</button>
</td>
</tr>
<tr>
<td data-label="ID">2</td>
<td data-label="Имя">Иван Сидоров</td>
<td data-label="Email">ivan@example.com</td>
<td data-label="Роль">Пользователь</td>
<td data-label="Действия">
<button class="btn btn-edit">Редактировать</button>
<button class="btn btn-delete">Удалить</button>
</td>
</tr>
</tbody>
</table>
CSS
.data-table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
font-size: 0.95rem;
text-align: left;
}
.data-table caption {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 0.5rem;
text-align: left;
}
.data-table th,
.data-table td {
padding: 0.75rem;
border-bottom: 1px solid #ddd;
}
.data-table th {
background-color: #f4f4f4;
font-weight: bold;
}
.data-table tbody tr:hover {
background-color: #f9f9f9;
}
.btn {
padding: 0.3rem 0.6rem;
margin-right: 0.3rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
}
.btn-edit {
background-color: #28a745;
color: white;
}
.btn-delete {
background-color: #dc3545;
color: white;
}
/* Адаптивность для мобильных */
@media (max-width: 600px) {
.data-table,
.data-table thead,
.data-table tbody,
.data-table th,
.data-table td,
.data-table tr {
display: block;
}
.data-table thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
.data-table td {
position: relative;
padding-left: 50%;
border: none;
border-bottom: 1px solid #eee;
}
.data-table td:before {
content: attr(data-label) ": ";
position: absolute;
left: 6px;
width: 45%;
padding-right: 10px;
font-weight: bold;
}
}
Пагинация
Пагинация позволяет разбивать длинные списки на страницы.
HTML
<nav aria-label="Навигация по страницам">
<ul class="pagination">
<li><a href="?page=1" aria-label="Первая страница">«</a></li>
<li><a href="?page=2" aria-label="Предыдущая страница">‹</a></li>
<li><a href="?page=1">1</a></li>
<li><a href="?page=2">2</a></li>
<li class="active"><span>3</span></li>
<li><a href="?page=4">4</a></li>
<li><a href="?page=5">5</a></li>
<li><a href="?page=4" aria-label="Следующая страница">›</a></li>
<li><a href="?page=10" aria-label="Последняя страница">»</a></li>
</ul>
</nav>
CSS
.pagination {
display: flex;
list-style: none;
padding: 0;
margin: 1.5rem 0;
justify-content: center;
gap: 0.25rem;
}
.pagination a,
.pagination span {
display: inline-block;
padding: 0.4rem 0.8rem;
text-decoration: none;
color: #007bff;
border: 1px solid #ddd;
border-radius: 4px;
transition: background-color 0.2s;
}
.pagination a:hover {
background-color: #e9ecef;
}
.pagination .active span {
background-color: #007bff;
color: white;
border-color: #007bff;
}
PHP-логика (пример)
<?php
$page = $_GET['page'] ?? 1;
$perPage = 10;
$totalItems = 123; // Например, из COUNT(*) в SQL
$totalPages = ceil($totalItems / $perPage);
$page = max(1, min($page, $totalPages));
$offset = ($page - 1) * $perPage;
// Запрос к БД с LIMIT и OFFSET
// SELECT * FROM users LIMIT $perPage OFFSET $offset
?>
Кнопки разных типов
Кнопки — ключевой элемент управления.
HTML
<div class="button-group">
<button class="btn-primary">Основная</button>
<button class="btn-secondary">Второстепенная</button>
<button class="btn-success">Успех</button>
<button class="btn-danger">Опасность</button>
<button class="btn-warning">Предупреждение</button>
<button class="btn-info">Информация</button>
<button class="btn-link">Ссылка</button>
</div>
CSS
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 1.5rem 0;
}
.btn-primary,
.btn-secondary,
.btn-success,
.btn-danger,
.btn-warning,
.btn-info,
.btn-link {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: opacity 0.2s;
}
.btn-primary { background-color: #007bff; color: white; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn-success { background-color: #28a745; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-warning { background-color: #ffc107; color: #212529; }
.btn-info { background-color: #17a2b8; color: white; }
.btn-link {
background: none;
color: #007bff;
text-decoration: underline;
padding: 0;
}
.btn-primary:hover { opacity: 0.9; }
.btn-link:hover { text-decoration: none; }
Вкладки (Tabs)
Вкладки — это интерфейсный паттерн, позволяющий переключаться между различными секциями контента без перезагрузки страницы. Они улучшают навигацию и организацию информации.
HTML
<div class="tabs-container">
<ul class="tabs-list" role="tablist">
<li role="presentation">
<button id="tab1" class="tab-button active" role="tab" aria-selected="true" aria-controls="panel1">Общее</button>
</li>
<li role="presentation">
<button id="tab2" class="tab-button" role="tab" aria-selected="false" aria-controls="panel2">Настройки</button>
</li>
<li role="presentation">
<button id="tab3" class="tab-button" role="tab" aria-selected="false" aria-controls="panel3">Помощь</button>
</li>
</ul>
<div id="panel1" class="tab-panel" role="tabpanel" aria-labelledby="tab1">
<p>Это содержимое вкладки "Общее". Здесь может быть любая информация: текст, формы, таблицы и т.д.</p>
</div>
<div id="panel2" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab2">
<p>Здесь находятся параметры настройки приложения или профиля пользователя.</p>
</div>
<div id="panel3" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab3">
<p>Раздел помощи содержит инструкции, часто задаваемые вопросы и ссылки на поддержку.</p>
</div>
</div>
CSS
.tabs-container {
max-width: 800px;
margin: 2rem auto;
font-family: Arial, sans-serif;
}
.tabs-list {
display: flex;
list-style: none;
padding: 0;
margin: 0 0 1rem 0;
border-bottom: 2px solid #ddd;
}
.tab-button {
padding: 0.75rem 1.5rem;
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
color: #555;
position: relative;
transition: color 0.2s;
}
.tab-button:hover {
color: #007bff;
}
.tab-button.active {
color: #007bff;
font-weight: bold;
}
.tab-button.active::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background-color: #007bff;
}
.tab-panel {
padding: 1.5rem;
background-color: #fff;
border: 1px solid #ddd;
border-top: none;
}
.hidden {
display: none;
}
JavaScript (опционально, для интерактивности)
<script>
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
// Убираем активность со всех кнопок и панелей
document.querySelectorAll('.tab-button').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hidden'));
// Делаем текущую вкладку активной
button.classList.add('active');
const panelId = button.getAttribute('aria-controls');
document.getElementById(panelId).classList.remove('hidden');
// Обновляем aria-selected
document.querySelectorAll('.tab-button').forEach(b => {
b.setAttribute('aria-selected', b === button ? 'true' : 'false');
});
});
});
</script>
📌 Внимание
Для доступности важно использовать ARIA-атрибуты (role,aria-selected,aria-controls,aria-labelledby). Это позволяет пользователям скринридеров корректно взаимодействовать с вкладками.
Простой калькулятор
Калькулятор — полезный элемент для демонстрации обработки пользовательского ввода и вычислений на стороне клиента или сервера.
HTML + PHP (серверная версия)
<form method="POST" action="calculator.php">
<h2>Калькулятор</h2>
<div class="form-group">
<input type="number" name="a" step="any" value="<?= htmlspecialchars($_POST['a'] ?? '') ?>" required placeholder="Первое число">
</div>
<div class="form-group">
<select name="op" required>
<option value="+" <?= ($_POST['op'] ?? '') === '+' ? 'selected' : '' ?>>+</option>
<option value="-" <?= ($_POST['op'] ?? '') === '-' ? 'selected' : '' ?>>−</option>
<option value="*" <?= ($_POST['op'] ?? '') === '*' ? 'selected' : '' ?>>×</option>
<option value="/" <?= ($_POST['op'] ?? '') === '/' ? 'selected' : '' ?>>÷</option>
</select>
</div>
<div class="form-group">
<input type="number" name="b" step="any" value="<?= htmlspecialchars($_POST['b'] ?? '') ?>" required placeholder="Второе число">
</div>
<button type="submit">Вычислить</button>
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST'): ?>
<?php
$a = $_POST['a'];
$b = $_POST['b'];
$op = $_POST['op'];
$result = null;
$error = null;
if (!is_numeric($a) || !is_numeric($b)) {
$error = 'Оба значения должны быть числами.';
} elseif ($op === '/' && $b == 0) {
$error = 'Деление на ноль невозможно.';
} else {
switch ($op) {
case '+': $result = $a + $b; break;
case '-': $result = $a - $b; break;
case '*': $result = $a * $b; break;
case '/': $result = $a / $b; break;
default: $error = 'Неизвестная операция.';
}
}
?>
<div class="calc-result">
<?php if ($error): ?>
<p style="color: #dc3545;"><?= htmlspecialchars($error) ?></p>
<?php else: ?>
<p><strong>Результат:</strong> <?= htmlspecialchars(number_format($result, 6, '.', '')) ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
</form>
CSS для калькулятора
form h2 {
text-align: center;
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group input,
.form-group select {
width: 100%;
padding: 0.6rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.calc-result {
margin-top: 1.5rem;
padding: 1rem;
background-color: #f8f9fa;
border-left: 4px solid #007bff;
}
💡 Совет
Серверная реализация гарантирует работу даже при отключённом JavaScript. Для более сложных калькуляторов (например, с историей или научными функциями) лучше использовать клиентский JavaScript.
Поля ввода разных типов
Современный HTML предоставляет множество типов <input>, улучшающих UX и валидацию.
HTML
<form class="input-demo">
<div class="form-row">
<label>Текст: <input type="text" placeholder="Имя"></label>
</div>
<div class="form-row">
<label>Email: <input type="email" placeholder="user@example.com"></label>
</div>
<div class="form-row">
<label>Пароль: <input type="password" placeholder="••••••••"></label>
</div>
<div class="form-row">
<label>Телефон: <input type="tel" placeholder="+7 (999) 123-45-67"></label>
</div>
<div class="form-row">
<label>Дата: <input type="date"></label>
</div>
<div class="form-row">
<label>Число: <input type="number" min="0" max="100" step="1"></label>
</div>
<div class="form-row">
<label>Цвет: <input type="color" value="#007bff"></label>
</div>
<div class="form-row">
<label>Файл: <input type="file" accept="image/*"></label>
</div>
<div class="form-row">
<label>Диапазон: <input type="range" min="0" max="100" value="50"></label>
</div>
</form>
CSS
.input-demo {
max-width: 500px;
margin: 2rem auto;
padding: 1.5rem;
background: #fafafa;
border-radius: 8px;
}
.form-row {
margin-bottom: 1rem;
}
.form-row label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #333;
}
.form-row input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}
⚠️ Предупреждение
Типыurl,numberи другие обеспечивают базовую валидацию на клиенте, но никогда не заменяют серверную проверку. Злоумышленник может легко обойти клиентские ограничения.
Форма обратной связи
Форма обратной связи — стандартный элемент веб-сайта, позволяющий пользователю отправить сообщение владельцу ресурса. Она часто используется для запросов поддержки, предложений или жалоб.
HTML
<form id="feedbackForm" method="POST" action="send_feedback.php">
<h2>Обратная связь</h2>
<div class="form-group">
<label for="name">Ваше имя</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="subject">Тема</label>
<select id="subject" name="subject" required>
<option value="">— Выберите тему —</option>
<td><option value="support">Техническая поддержка</option></td>
<td><option value="suggestion">Предложение</option></td>
<td><option value="complaint">Жалоба</option></td>
<td><option value="other">Другое</option></td>
</select>
</div>
<div class="form-group">
<label for="message">Сообщение</label>
<textarea id="message" name="message" rows="6" required></textarea>
</div>
<button type="submit">Отправить</button>
</form>
CSS
#feedbackForm {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
#feedbackForm h2 {
text-align: center;
margin-bottom: 1.5rem;
color: #333;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #444;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.6rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
button[type="submit"] {
width: 100%;
padding: 0.8rem;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
button[type="submit"]:hover {
background-color: #218838;
}
PHP (send_feedback.php)
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$subject = $_POST['subject'] ?? '';
$message = trim($_POST['message'] ?? '');
// Валидация
if (empty($name) || empty($email) || empty($subject) || empty($message)) {
die('Все поля обязательны.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}
if (!in_array($subject, ['support', 'suggestion', 'complaint', 'other'])) {
die('Недопустимая тема.');
}
// Здесь должна быть отправка письма или сохранение в БД
// Пример:
// mail('admin@example.com', "Обратная связь: $subject", $message, "From: $email");
echo '<p style="color: green; text-align: center;">Сообщение успешно отправлено!</p>';
}
?>
📌 Внимание
На продакшене используйте библиотеки вроде PHPMailer или SwiftMailer для надёжной отправки почты и защиты от спама. Обязательно добавьте защиту от CSRF и rate limiting.
Поиск по сайту
Простая форма поиска позволяет пользователю искать контент на сайте.
HTML
<form class="search-form" method="GET" action="search.php">
<div class="search-container">
<input type="text" name="q" placeholder="Поиск по сайту…" required>
<button type="submit">🔍</button>
</div>
</form>
CSS
.search-form {
display: flex;
justify-content: center;
margin: 2rem 0;
}
.search-container {
display: flex;
width: 100%;
max-width: 500px;
border: 1px solid #ccc;
border-radius: 24px;
overflow: hidden;
}
.search-container input {
flex: 1;
padding: 0.75rem 1rem;
border: none;
font-size: 1rem;
outline: none;
}
.search-container button {
background: #007bff;
color: white;
border: none;
padding: 0 1.25rem;
cursor: pointer;
font-size: 1.1rem;
}
.search-container button:hover {
background: #0056b3;
}
PHP (search.php)
<?php
$q = trim($_GET['q'] ?? '');
if (empty($q)) {
die('Введите поисковый запрос.');
}
// Пример простого поиска по массиву (в реальности — по БД)
$pages = [
['title' => 'Главная', 'content' => 'Добро пожаловать на сайт'],
['title' => 'О нас', 'content' => 'Мы занимаемся разработкой'],
['title' => 'Контакты', 'content' => 'Напишите нам']
];
$results = array_filter($pages, fn($page) =>
stripos($page['title'], $q) !== false ||
stripos($page['content'], $q) !== false
);
?>
<h2>Результаты поиска по запросу: "<?= htmlspecialchars($q) ?>"</h2>
<?php if (count($results)): ?>
<ul>
<?php foreach ($results as $page): ?>
<li><strong><?= htmlspecialchars($page['title']) ?></strong>: <?= htmlspecialchars($page['content']) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>Ничего не найдено.</p>
<?php endif; ?>
💡 Совет
Для полноценного поиска используйте Elasticsearch, Sphinx или хотя быFULLTEXTиндексы в MySQL.
Карточка товара / элемент списка
Часто используется в интернет-магазинах, каталогах, блогах.
HTML
<article class="product-card">
<img src="product.jpg" alt="Название товара" loading="lazy">
<div class="product-info">
<h3>Название товара</h3>
<p class="price">1 990 ₽</p>
<p class="description">Краткое описание товара, его особенности и преимущества.</p>
<button class="btn btn-primary">В корзину</button>
</div>
</article>
CSS
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: white;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.product-card img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
.product-info {
padding: 1rem;
}
.product-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.1rem;
color: #333;
}
.price {
font-weight: bold;
color: #d32f2f;
margin: 0 0 0.75rem 0;
font-size: 1.2rem;
}
.description {
color: #666;
margin: 0 0 1rem 0;
font-size: 0.95rem;
line-height: 1.4;
}
.btn-primary {
background: #1976d2;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
Страница профиля пользователя
Страница профиля — это персональное пространство, где отображаются данные о пользователе, его активность и настройки. Она часто включает аватар, контактную информацию, историю действий и возможность редактирования данных.
HTML
<div class="profile-page">
<div class="profile-header">
<img src="avatar.jpg" alt="Аватар пользователя" class="avatar" loading="lazy">
<h1>Анна Петрова</h1>
<p class="user-role">Администратор</p>
</div>
<div class="profile-section">
<h2>Контактная информация</h2>
<ul class="profile-info">
<li><strong>Email:</strong> anna@example.com</li>
<li><strong>Телефон:</strong> +7 (999) 123-45-67</li>
<li><strong>Регистрация:</strong> 15 января 2024</li>
</ul>
</div>
<div class="profile-section">
<h2>Действия</h2>
<ul class="activity-list">
<li>Изменил(а) пароль — 1 апреля 2026</li>
<li>Вошёл(ла) в систему — 3 апреля 2026</li>
<li>Создал(а) новую статью — 28 марта 2026</li>
</ul>
</div>
<div class="profile-actions">
<a href="edit-profile.php" class="btn btn-primary">Редактировать профиль</a>
<a href="logout.php" class="btn btn-secondary">Выйти</a>
</div>
</div>
CSS
.profile-page {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.profile-header {
text-align: center;
margin-bottom: 2rem;
}
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #f0f0f0;
margin-bottom: 1rem;
}
.profile-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}
.user-role {
color: #666;
font-style: italic;
margin-top: 0.25rem;
}
.profile-section {
margin-bottom: 2rem;
}
.profile-section h2 {
font-size: 1.4rem;
margin-bottom: 1rem;
color: #222;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}
.profile-info,
.activity-list {
list-style: none;
padding: 0;
}
.profile-info li,
.activity-list li {
padding: 0.5rem 0;
border-bottom: 1px solid #f5f5f5;
}
.profile-info strong {
color: #333;
}
.profile-actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1.5rem;
}
.btn {
padding: 0.6rem 1.2rem;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: opacity 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn:hover {
opacity: 0.9;
}
📌 Внимание
На продакшене аватары следует загружать через защищённые маршруты с проверкой MIME-типов и ограничением размера файла.
Модальное окно (Modal)
Модальные окна используются для показа важной информации, подтверждения действий или сбора данных без перехода на другую страницу.
HTML
<!-- Кнопка вызова -->
<button id="openModalBtn">Открыть модальное окно</button>
<!-- Само модальное окно -->
<div id="myModal" class="modal" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Подтверждение действия</h2>
<button type="button" class="close" aria-label="Закрыть">×</button>
</div>
<div class="modal-body">
<p>Вы уверены, что хотите удалить этот элемент? Это действие нельзя отменить.</p>
</div>
<div class="modal-footer">
<button id="confirmBtn" class="btn btn-danger">Удалить</button>
<button id="cancelBtn" class="btn btn-secondary">Отмена</button>
</div>
</div>
</div>
</div>
CSS
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
animation: fadeIn 0.3s;
}
.modal-dialog {
position: relative;
margin: 10% auto;
max-width: 500px;
animation: slideIn 0.3s;
}
.modal-content {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
.modal-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 {
margin: 0;
font-size: 1.4rem;
color: #333;
}
.close {
font-size: 1.8rem;
background: none;
border: none;
cursor: pointer;
color: #999;
}
.close:hover {
color: #333;
}
.modal-body {
padding: 1.5rem;
color: #555;
}
.modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
JavaScript
<script>
const modal = document.getElementById('myModal');
const openBtn = document.getElementById('openModalBtn');
const closeBtn = document.querySelector('.close');
const cancelBtn = document.getElementById('cancelBtn');
function openModal() {
modal.style.display = 'block';
modal.setAttribute('aria-hidden', 'false');
}
function closeModal() {
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
}
openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
// Закрытие по клику вне окна
window.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
</script>
💡 Совет
Для доступности важно управлять фокусом: при открытии модального окна фокус должен перемещаться внутрь него, а при закрытии — возвращаться к вызвавшему элементу.
Форма регистрации
Форма регистрации позволяет новому пользователю создать аккаунт.
HTML
<form id="registerForm" method="POST" action="register.php" novalidate>
<h2>Регистрация</h2>
<div class="form-group">
<label for="regName">Имя</label>
<input type="text" id="regName" name="name" required minlength="2" maxlength="50">
</div>
<div class="form-group">
<label for="regEmail">Email</label>
<input type="email" id="regEmail" name="email" required>
</div>
<div class="form-group">
<label for="regPassword">Пароль</label>
<input type="password" id="regPassword" name="password" required minlength="8">
<small class="hint">Минимум 8 символов, включая цифру и заглавную букву.</small>
</div>
<div class="form-group">
<label for="regConfirm">Подтверждение пароля</label>
<input type="password" id="regConfirm" name="confirm" required>
</div>
<button type="submit">Зарегистрироваться</button>
<p class="form-footer">
Уже есть аккаунт? <a href="login.php">Войти</a>
</p>
</form>
CSS (частично совпадает с формой входа)
#registerForm {
max-width: 450px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
.hint {
display: block;
margin-top: 0.25rem;
font-size: 0.85rem;
color: #666;
}
PHP (register.php)
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$password = $_POST['password'] ?? '';
$confirm = $_POST['confirm'] ?? '';
// Валидация
if (empty($name) || empty($email) || empty($password) || empty($confirm)) {
die('Все поля обязательны.');
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}
if ($password !== $confirm) {
die('Пароли не совпадают.');
}
if (strlen($password) < 8) {
die('Пароль должен содержать минимум 8 символов.');
}
// Проверка сложности пароля (пример)
if (!preg_match('/[A-Z]/', $password) || !preg_match('/[0-9]/', $password)) {
die('Пароль должен содержать хотя бы одну заглавную букву и одну цифру.');
}
// Проверка уникальности email (в реальном проекте — запрос к БД)
// if (emailExists($email)) { die('Пользователь с таким email уже существует.'); }
// Хеширование пароля
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// Сохранение в БД
// insertUser($name, $email, $hashedPassword);
echo 'Регистрация успешна! <a href="login.php">Войдите</a>.';
}
?>
⚠️ Предупреждение
Никогда не храните пароли в открытом виде. Используйтеpassword_hash()иpassword_verify().
Форма обратной связи с валидацией на стороне клиента
Для улучшения пользовательского опыта можно добавить базовую валидацию прямо в браузере, не дожидаясь ответа сервера. Это снижает нагрузку на сервер и делает взаимодействие более отзывчивым.
HTML + JavaScript
<form id="feedbackFormJS" novalidate>
<h2>Обратная связь (с клиентской проверкой)</h2>
<div class="form-group">
<label for="jsName">Ваше имя</label>
<input type="text" id="jsName" name="name" required minlength="2" maxlength="50">
<span class="error" id="nameError"></span>
</div>
<div class="form-group">
<label for="jsEmail">Email</label>
<input type="email" id="jsEmail" name="email" required>
<span class="error" id="emailError"></span>
</div>
<div class="form-group">
<label for="jsSubject">Тема</label>
<select id="jsSubject" name="subject" required>
<option value="">— Выберите тему —</option>
<option value="support">Техническая поддержка</option>
<option value="suggestion">Предложение</option>
<option value="complaint">Жалоба</option>
<option value="other">Другое</option>
</select>
<span class="error" id="subjectError"></span>
</div>
<div class="form-group">
<label for="jsMessage">Сообщение</label>
<textarea id="jsMessage" name="message" rows="6" required minlength="10"></textarea>
<span class="error" id="messageError"></span>
</div>
<button type="submit">Отправить</button>
</form>
CSS (дополнение)
.error {
display: block;
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
min-height: 1.2rem;
}
JavaScript
<script>
document.getElementById('feedbackFormJS').addEventListener('submit', function(e) {
e.preventDefault();
let valid = true;
// Очистка ошибок
document.querySelectorAll('.error').forEach(el => el.textContent = '');
// Проверка имени
const name = document.getElementById('jsName');
if (!name.value.trim() || name.value.length < 2) {
document.getElementById('nameError').textContent = 'Имя должно содержать минимум 2 символа.';
valid = false;
}
// Проверка email
const email = document.getElementById('jsEmail');
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email.value)) {
document.getElementById('emailError').textContent = 'Введите корректный email.';
valid = false;
}
// Проверка темы
const subject = document.getElementById('jsSubject');
if (!subject.value) {
document.getElementById('subjectError').textContent = 'Выберите тему обращения.';
valid = false;
}
// Проверка сообщения
const message = document.getElementById('jsMessage');
if (!message.value.trim() || message.value.length < 10) {
document.getElementById('messageError').textContent = 'Сообщение должно содержать минимум 10 символов.';
valid = false;
}
if (valid) {
alert('Форма отправлена! (В реальном проекте — AJAX-запрос)');
// Здесь можно отправить форму через fetch()
// fetch('send_feedback.php', { method: 'POST', body: new FormData(this) })
}
});
</script>
💡 Совет
Клиентская валидация — это удобство, а не защита. Всегда дублируйте проверки на сервере.
Простой список задач (To-Do List)
Элементарный интерфейс для управления персональными задачами.
HTML
<div class="todo-app">
<h2>Список задач</h2>
<form id="addTaskForm">
<input type="text" id="newTask" placeholder="Новая задача…" required>
<button type="submit">+</button>
</form>
<ul id="taskList">
<!-- Задачи будут добавляться сюда -->
</ul>
</div>
CSS
.todo-app {
max-width: 500px;
margin: 2rem auto;
padding: 1.5rem;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
}
.todo-app h2 {
text-align: center;
margin-bottom: 1.25rem;
}
#addTaskForm {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
#addTaskForm input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}
#addTaskForm button {
padding: 0.5rem 1rem;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
#taskList {
list-style: none;
padding: 0;
}
#taskList li {
display: flex;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid #eee;
}
#taskList input[type="checkbox"] {
margin-right: 0.75rem;
}
.task-text {
flex: 1;
font-size: 1rem;
}
.task-text.completed {
text-decoration: line-through;
color: #888;
}
.delete-btn {
background: none;
border: none;
color: #dc3545;
font-size: 1.2rem;
cursor: pointer;
padding: 0 0.5rem;
}
JavaScript
<script>
const taskList = document.getElementById('taskList');
const addTaskForm = document.getElementById('addTaskForm');
// Загрузка задач из localStorage
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
renderTasks();
function renderTasks() {
taskList.innerHTML = '';
tasks.forEach((task, index) => {
const li = document.createElement('li');
li.innerHTML = `
`;
taskList.appendChild(li);
});
}
addTaskForm.addEventListener('submit', function(e) {
e.preventDefault();
const input = document.getElementById('newTask');
const text = input.value.trim();
if (text) {
tasks.push({ text, completed: false });
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
input.value = '';
}
});
taskList.addEventListener('click', function(e) {
if (e.target.matches('input[type="checkbox"]')) {
const index = e.target.dataset.index;
tasks[index].completed = e.target.checked;
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
}
if (e.target.matches('.delete-btn')) {
const index = e.target.dataset.index;
tasks.splice(index, 1);
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
}
});
</script>
📌 Внимание
Данные хранятся вlocalStorage, что подходит для личного использования, но не для многопользовательских систем. Для общего доступа требуется серверное хранилище.
Страница восстановления пароля
Стандартный элемент безопасности, позволяющий пользователю сбросить забытый пароль.
HTML
<form id="resetForm" method="POST" action="reset_password.php">
<h2>Восстановление пароля</h2>
<p>Введите ваш email, и мы вышлем ссылку для сброса пароля.</p>
<div class="form-group">
<label for="resetEmail">Email</label>
<input type="email" id="resetEmail" name="email" required>
</div>
<button type="submit">Отправить ссылку</button>
<p class="form-footer">
<a href="login.php">← Назад к входу</a>
</p>
</form>
CSS
#resetForm {
max-width: 450px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
#resetForm h2 {
text-align: center;
margin-bottom: 0.5rem;
}
#resetForm p {
text-align: center;
color: #666;
margin-bottom: 1.5rem;
}
PHP (reset_password.php)
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}
// Проверка существования пользователя (в реальности — запрос к БД)
// if (!userExists($email)) { ... }
// Генерация токена (пример)
$token = bin2hex(random_bytes(32));
// Сохранение токена в БД с временем жизни (например, 1 час)
// Отправка письма (в реальности — через PHPMailer и т.п.)
// $link = "https://example.com/set_new_password.php?token=$token";
// mail($email, "Сброс пароля", "Перейдите по ссылке: $link");
echo '<p style="color: green; text-align: center;">Ссылка для сброса пароля отправлена на ваш email.</p>';
}
?>
⚠️ Предупреждение
Никогда не сообщайте пользователю, существует ли email в системе. Всегда выводите нейтральное сообщение, чтобы избежать перебора учётных записей.
Форма загрузки файла
Загрузка файлов — частая задача в веб-приложениях: аватары, документы, изображения и т.д.
HTML
<form id="uploadForm" method="POST" enctype="multipart/form-data" action="upload.php">
<h2>Загрузка файла</h2>
<div class="form-group">
<label for="fileInput">Выберите файл</label>
<input type="file" id="fileInput" name="uploaded_file" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx" required>
<small class="hint">Поддерживаются: JPG, PNG, PDF, DOC/DOCX (макс. 5 МБ)</small>
</div>
<button type="submit">Загрузить</button>
</form>
CSS
#uploadForm {
max-width: 500px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
#uploadForm h2 {
text-align: center;
margin-bottom: 1.5rem;
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
}
.form-group input[type="file"] {
width: 100%;
padding: 0.5rem 0;
font-size: 1rem;
}
.hint {
display: block;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #666;
}
PHP (upload.php)
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_FILES['uploaded_file'])) {
die('Файл не был отправлен.');
}
$file = $_FILES['uploaded_file'];
$uploadDir = __DIR__ . '/uploads/';
$maxSize = 5 * 1024 * 1024; // 5 МБ
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
// Проверка ошибок
if ($file['error'] !== UPLOAD_ERR_OK) {
die('Ошибка при загрузке файла.');
}
// Проверка размера
if ($file['size'] > $maxSize) {
die('Файл слишком большой (макс. 5 МБ).');
}
// Проверка типа
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $allowedTypes)) {
die('Недопустимый тип файла.');
}
// Генерация безопасного имени
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . strtolower($extension);
$targetPath = $uploadDir . $safeName;
// Создание директории, если её нет
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// Перемещение файла
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "Файл успешно загружен! <br>Путь: " . htmlspecialchars($targetPath);
} else {
die('Не удалось сохранить файл.');
}
}
?>
⚠️ Предупреждение
Никогда не используйте оригинальное имя файла напрямую ($_FILES['name']) — это уязвимость. Всегда генерируйте уникальное безопасное имя.
Уведомления (Notifications)
Всплывающие сообщения для информирования пользователя об успехе, ошибках или предупреждениях.
HTML
<!-- Контейнер для уведомлений -->
<div id="notificationContainer"></div>
<!-- Пример вызова -->
<button onclick="showNotification('success', 'Данные успешно сохранены!')">Показать успех</button>
<button onclick="showNotification('error', 'Произошла ошибка при отправке.')">Показать ошибку</button>
<button onclick="showNotification('warning', 'Пароль слишком слабый.')">Показать предупреждение</button>
CSS
#notificationContainer {
position: fixed;
top: 20px;
right: 20px;
z-index: 2000;
max-width: 400px;
}
.notification {
padding: 1rem 1.5rem;
margin-bottom: 0.75rem;
border-radius: 4px;
color: white;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideInRight 0.3s ease-out;
display: flex;
justify-content: space-between;
align-items: center;
}
.notification.success { background-color: #28a745; }
.notification.error { background-color: #dc3545; }
.notification.warning { background-color: #ffc107; color: #212529; }
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.close-notification {
background: none;
border: none;
color: inherit;
font-size: 1.2rem;
cursor: pointer;
padding: 0 0.5rem;
}
JavaScript
<script>
function showNotification(type, message, duration = 5000) {
const container = document.getElementById('notificationContainer');
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
`;
container.appendChild(notification);
// Автоматическое скрытие
const timer = setTimeout(() => {
removeNotification(notification);
}, duration);
// Закрытие по клику
notification.querySelector('.close-notification').addEventListener('click', () => {
clearTimeout(timer);
removeNotification(notification);
});
}
function removeNotification(el) {
el.style.animation = 'slideOutRight 0.3s forwards';
setTimeout(() => el.remove(), 300);
}
// Анимация исчезновения
const style = document.createElement('style');
style.textContent = `
@keyframes slideOutRight {
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
</script>
💡 Совет
Для доступности добавьтеrole="alert"к уведомлениям об ошибках иrole="status"— к информационным.
Страница «404 — Не найдено»
Стандартная страница ошибки, отображаемая при обращении к несуществующему URL.
HTML
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Страница не найдена</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
.error-page {
text-align: center;
max-width: 600px;
padding: 2rem;
}
.error-code {
font-size: 6rem;
font-weight: bold;
color: #dc3545;
margin: 0 0 1rem 0;
}
.error-message {
font-size: 1.5rem;
margin: 0 0 2rem 0;
color: #666;
}
.btn-home {
display: inline-block;
padding: 0.6rem 1.5rem;
background: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 1.1rem;
transition: background 0.2s;
}
.btn-home:hover {
background: #0056b3;
}
</style>
</head>
<body>
<div class="error-page">
<div class="error-code">404</div>
<p class="error-message">Страница не найдена</p>
<p>К сожалению, запрашиваемая вами страница не существует.</p>
<a href="/" class="btn-home">На главную</a>
</div>
</body>
</html>
📌 Важно
Сервер должен возвращать HTTP-статус 404, а не просто показывать страницу с таким текстом. Иначе поисковые системы будут считать её валидной.